/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package grex;

import grex.fitnessfunctions.ErrorFunctions.ErrorCalculator;
import grex.fitnessfunctions.ErrorFunctions.IErrorFunction;
import grex.genes.GeneException;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author RIK
 */
public class ConformalCalculator {

    private interface IConformalMetric {

        public double calc(Prediction p, int clas);
    }

    private class conformalMargin implements IConformalMetric {

        public double calc(Prediction p, int clas) {
            double[] probs = p.getProbs();
            double max = 0;
            for (int i = 0; i < probs.length; i++) {
                if (i != clas) {
                    if (probs[i] > max) {
                        max = probs[i];
                    }
                }
            }
            return p.getProbForClass(clas) - max;
        }
    }

    private class conformalNormalProb implements IConformalMetric {

        public double calc(Prediction p, int clas) {
            return p.getProbForClass(clas);
        }
    }
    private IConformalMetric conformalMetric;
    double alpha, p, tieSmoother;
    ArrayList<ProbClassPair> calibrationSet;
    PrintWriter pw;
    ErrorCalculator ec;
    public ConformalCalculator(double alpha) {
        this.alpha = alpha;
        conformalMetric = new conformalMargin();
        try {
            pw = new PrintWriter("Conformal_result" + System.currentTimeMillis()+".txt");
            pw.println("Conformal e:" + alpha);
            pw.println("Dataset\tModel\tMetric\tValue");
            ec = new ErrorCalculator(Options.CLASSIFICATION);
            //conformalMetric = new conformalNormalProb();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(ConformalCalculator.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    public void closeFile(){
        pw.close();
    }

    public void calcResult(HashMap<String, ArrayList<IPredictiveModel>> predictiveModels, Options ops) {
        try {
                        
            for (String name : predictiveModels.keySet()) {
                ArrayList<IPredictiveModel> models = predictiveModels.get(name);
                double validity = 0, zeroC = 0, oneC = 0, multC = 0,oneAcc = 0,size = 0, acc = 0, auc = 0, bri=0, boCof=0;
                for (IPredictiveModel model : models) {
                    initP(model);
                    HashMap<Prediction, ArrayList<Double>> aClasses = getAcceptedClasses(model.getPcTest());
                    validity += calcValidity(aClasses) / models.size();
                    HashMap<Integer, Double> count = getPredictionDistribution(aClasses);
                    oneAcc += calcAccOne(aClasses) / models.size();
                    size += model.getNrOfNodes() / models.size();
                    acc +=ec.calcError(model.getPcTest(),IErrorFunction.ACCURACY)/models.size()/100;
                    auc +=ec.calcError(model.getPcTest(),IErrorFunction.AUC)/models.size()/100;
                    bri +=ec.calcError(model.getPcTest(),IErrorFunction.BRI)/models.size()/100;
                    boCof += calcBoCof(aClasses)/models.size();
                    for (Integer c : count.keySet()) {
                        if (c == 0) {
                            zeroC += count.get(c) / models.size();
                        } else if (c == 1) {
                            oneC += count.get(c) / models.size();
                        } else {
                            multC += count.get(c) / models.size();
                        }
                    }
                    

                }
                pw.println(ops.getDATA_FILE() + "\t" + name + "\tval:\t" + validity);
                pw.println(ops.getDATA_FILE() + "\t" + name + "\toneAcc:\t" + oneAcc);
                pw.println(ops.getDATA_FILE() + "\t" + name + "\tzeroC:\t" + zeroC);
                pw.println(ops.getDATA_FILE() + "\t" + name + "\toneC:\t" + oneC);
                pw.println(ops.getDATA_FILE() + "\t" + name + "\tboCof:\t" + boCof);
                pw.println(ops.getDATA_FILE() + "\t" + name + "\tmultiC:\t" + multC);
                pw.println(ops.getDATA_FILE() + "\t" + name + "\tacc:\t" + (1.0-acc));
                pw.println(ops.getDATA_FILE() + "\t" + name + "\tauc:\t" + (1.0-auc));
                pw.println(ops.getDATA_FILE() + "\t" + name + "\tbri:\t" + bri);
                pw.println(ops.getDATA_FILE() + "\t" + name + "\tsize:\t" + size);


            }
       
        } catch (Exception e) {
            grex.ErrorManager.getInstance().reportError(e);
        }
    }
    
    public double calcBoCof(HashMap<Prediction, ArrayList<Double>> acceptedClasses){
        double boCof = 0;
        for (Prediction prediction : acceptedClasses.keySet()) {
            ArrayList<Double> classes = acceptedClasses.get(prediction);
            boCof += classes.size();
        }
        return boCof/acceptedClasses.size();
    }
    public double calcAccOne(HashMap<Prediction, ArrayList<Double>> acceptedClasses){
        double oneAcc=0;
                
        int count = 0;
        for (Prediction prediction : acceptedClasses.keySet()) {
            ArrayList<Double> classes = acceptedClasses.get(prediction);
            if(classes.size()==1){
                count++;
                double c = classes.get(0);
                if(c==prediction. getTargetValue())
                    oneAcc++;
            }
            
        }             
        if(count==0)
            return 0;
        return oneAcc/count;
    }
    
    int totalTies;

    public void initP(IPredictiveModel model) {
        model.execute(model.getPcVal());
        model.execute(model.getPcTest());
        calibrationSet = new ArrayList<ProbClassPair>(model.getPcVal().size());
        for (Prediction pr : model.getPcVal().values()) {


            double prob = conformalMetric.calc(pr, (int) pr.getTargetValue());

            double pred = pr.getTargetValue();
            calibrationSet.add(new ProbClassPair(prob, pred));
        }
        Collections.sort(calibrationSet);


        int instanceAlpha = (int) Math.ceil(alpha * calibrationSet.size());
        if(instanceAlpha==calibrationSet.size())
            instanceAlpha--;
        p = calibrationSet.get(instanceAlpha).getP();

        double tieIn = 0, tieEx = 0;
        int i = instanceAlpha;
        while (i > 0 && calibrationSet.get(i).getP() == p) {
            tieIn++;
            i--;
        }
        i = instanceAlpha + 1;
        while (i < calibrationSet.size() && calibrationSet.get(i).getP() == p) {
            tieEx++;
            i++;
        }

        /**
         * +1 i tieIn för att: Om vi har följande i ordning x x o o o o o från
         * början där x är de som är out så borde ju följande 2 (#) vara de som
         * innebär att den inte kommer med # x # x o o o o o medan följande 6
         * innebär att den inte kommer med: x x # o # o # o # o # o #
         */
        tieSmoother = (tieIn + 1) / (1 + tieIn + tieEx);


    }

    private HashMap<Prediction, ArrayList<Double>> getAcceptedClasses(PredictionContainer pc) {
        HashMap<Prediction, ArrayList<Double>> acceptedClasses = new HashMap<Prediction, ArrayList<Double>>(pc.size());
        for (Prediction prediction : pc.values()) {
            ArrayList<Double> classes = new ArrayList<Double>();
            double[] probs = prediction.getProbs();
            for (int c = 0; c < probs.length; c++) {
                double value = conformalMetric.calc(prediction, c);
                if (value > p || value == p && Options.rnd.nextDouble() < tieSmoother) {
                    classes.add((double) c);
                }
            }
            acceptedClasses.put(prediction, classes);
        }
        return acceptedClasses;
    }

    private double calcValidity(HashMap<Prediction, ArrayList<Double>> acceptedClasses) {
        double val = 0;
        for (Prediction prediction : acceptedClasses.keySet()) {
            ArrayList<Double> classes = acceptedClasses.get(prediction);
            for (double c : classes) {
                if (c == prediction.getTargetValue()) {
                    val++;
                    break;
                }
            }
        }
        return val / acceptedClasses.size();
    }

    private HashMap<Integer, Double> getPredictionDistribution(HashMap<Prediction, ArrayList<Double>> acceptedClasses) {
        HashMap<Integer, Double> dist = new HashMap<Integer, Double>();
        for (ArrayList<Double> classes : acceptedClasses.values()) {
            int preds = classes.size();
            Double count = dist.get(preds);
            if (count == null) {
                count = new Double(0);
            }
            count++;
            dist.put(preds, count);

        }
        for (Integer count : dist.keySet()) {
            double c = dist.get(count) / acceptedClasses.size();
            dist.put(count, c);
        }
        return dist;
    }

    class ProbClassPair implements Comparable, Serializable {

        double p, c;

        public ProbClassPair(double p, double c) {
            this.p = p;
            this.c = c;
        }

        public int compareTo(Object o) {
            ProbClassPair pv = (ProbClassPair) o;
            if (p > pv.getP()) {
                return -1;
            }
            if (p < pv.getP()) {
                return 1;
            }
            return 0;
        }

        public double getP() {
            return p;
        }

        public double getC() {
            return c;
        }

        public String toString() {
            return "prob: " + p + " class: " + c;
        }
    }
}
